 /*******************************************************************************
  * Copyright (c) 2000, 2004 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Common Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/cpl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.runtime;

 import java.io.*;

 /**
  * This class should be used when there's a file already in the
  * destination and we don't want to lose its contents if a
  * failure writing this stream happens.
  * Basically, the new contents are written to a temporary location.
  * If everything goes OK, it is moved to the right place.
  * The user has the option to define the temporary location or
  * it will be created in the default-temporary directory
  * (see java.io.File for details).
  */
 public class SafeFileOutputStream extends OutputStream {
     protected File temp;
     protected File target;
     protected OutputStream output;
     protected boolean failed;
     protected static final String EXTENSION = ".bak"; //$NON-NLS-1$

     public SafeFileOutputStream(File file) throws IOException {
         this(file.getAbsolutePath(), null);
     }

     public SafeFileOutputStream(String targetName) throws IOException {
         this(targetName, null);
     }

     /**
      * If targetPath is null, the file will be created in the default-temporary directory.
      */
     public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
         failed = false;
         target = new File(targetPath);
         createTempFile(tempPath);
         // If we do not have a file at target location, but we do have at temp location,
 // it probably means something wrong happened the last time we tried to write it.
 // So, try to recover the backup file. And, if successful, write the new one.
 if (!target.exists() && !temp.exists()) {
             output = new BufferedOutputStream(new FileOutputStream(target));
             return;
         }
         copy(temp, target);
         output = new BufferedOutputStream(new FileOutputStream(temp));
     }

     public void close() throws IOException {
         close(false);
     }
     
     public void close(boolean discard) throws IOException {
         try {
             output.close();
         } catch (IOException e) {
             failed = true;
             throw e; // rethrow
 }
         if (discard || failed)
             temp.delete();
         else
             commit();
     }

     protected void commit() throws IOException {
         if (!temp.exists())
             return;
         target.delete();
         copy(temp, target);
         temp.delete();
     }

     protected void copy(File sourceFile, File destinationFile) throws IOException {
         if (!sourceFile.exists())
             return;
         if (sourceFile.renameTo(destinationFile))
             return;
         InputStream source = new BufferedInputStream(new FileInputStream(sourceFile));
         OutputStream destination = new BufferedOutputStream(new FileOutputStream(destinationFile));
         transferStreams(source, destination);
     }

     protected void createTempFile(String tempPath) {
         if (tempPath == null)
             tempPath = target.getAbsolutePath() + EXTENSION;
         temp = new File(tempPath);
     }
     
     public void flush() throws IOException {
         try {
             output.flush();
         } catch (IOException e) {
             failed = true;
             throw e; // rethrow
 }
     }

     public String getTempFilePath() {
         return temp.getAbsolutePath();
     }

     protected void transferStreams(InputStream source, OutputStream destination) throws IOException {
         try {
             byte[] buffer = new byte[8192];
             while (true) {
                 int bytesRead = source.read(buffer);
                 if (bytesRead == -1)
                     break;
                 destination.write(buffer, 0, bytesRead);
             }
         } finally {
             try {
                 source.close();
             } catch (IOException e) {
                 // do nothing
 }
             try {
                 destination.close();
             } catch (IOException e) {
                 // do nothing
 }
         }
     }

     public void write(int b) throws IOException {
         try {
             output.write(b);
         } catch (IOException e) {
             failed = true;
             throw e; // rethrow
 }
     }
 }
